﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Collections;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Media;
using OpenTK.Graphics;
using OpenTK.Platform;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Reflection;
using System.Diagnostics;
namespace Chip8
{
    public partial class frmEmu : Form
    {
       
        public Chip8System MainSystem = new Chip8System();
        public class Chip8System
        {
            public Stack Stack; //System Stack
            public ushort PC; //Program Counter
            public byte[] Memory = new byte[4096]; //4k System Memory
            public byte[] Vreg = new byte[16]; //15 8bit regests V0-VE, VF is used as a flag
            public byte[] regRPL = new byte[8];  //RPL register
            public ushort AddressRegister; //Index Register
            public ushort Opcode; //Current Opcode
            public byte[] FrameBuffer = new byte[64 * 32]; //Graphics Array 1 = on, 0 = off, 2048 pixels total;
            public byte Delay_Timer; //Delay Timer - Counts down to 0 at 60hz, if 0 then game then sets it until the next time tested for 0.
            public byte Sound_Timer; //Sound Timer - Counts down to 0 at 60hz, if 0 we play a beep. The game then sets it until the next time tested for 0.
        }

        public Instruction instr; //General Declaration of our instruction breakdown.
        public struct Instruction //Instruction Breakdown
        {
            public ushort raw;
            public ushort opnum;
            public ushort nnn;
            public byte n;
            public byte x;
            public byte y;
            public byte kk;
        }

        public EventWaitHandle m_DrawWait = new EventWaitHandle(false, EventResetMode.AutoReset); //EventWaitHandle to halt CPU, and allow open GL to fully draw every frame.
        EventWaitHandle EmulationExecution = new EventWaitHandle(false, EventResetMode.AutoReset); //Allows intepreter to finish processing before called again.
        public byte[] ResetMemory = new byte[4096]; //Reset Array used to copy to system memory when a game is reset.
        Random r = new Random(); //Random used in 0xC000 RND instruction
        public byte LastKeyPressed = 0x11;   //Keypad - Stores Last Key Pressed
        public bool StopEmulation = true; //Stop emulation flag.
        public bool PauseEmulation; //Pause emulation flag.
        public Thread EmuThread; //Emulation Thread
        public String RomName; //Name of Loaded Rom
        public bool BreakPoint;
        public bool StepInto;
        public bool StepOver;
        public bool StepOut;
        public ushort StepOutValue=0;
        public int EmulationSpeed = 10;
        public bool EnableDeflicker = false;
        public int ScreenWidth = 64;
        public int ScreenHeight = 32;
        public int FPS = 0;
        public string MD5Hash = "";
        public frmDebugger DB = new frmDebugger();
        public frmMemory MV = new frmMemory();
        public frmCheats CH = new frmCheats();

        //OpenTK
        private Control m_ControlContext;
        private GraphicsContext m_GContext;
        private IWindowInfo m_WindowInfo;
        
        public void InitializeSystem()
        {
            Array.Clear(MainSystem.Memory, 0, MainSystem.Memory.Length);
            MainSystem.PC = 512; //Rom begins at 0x200
            MainSystem.AddressRegister = new ushort();
            Array.Clear(MainSystem.Vreg, 0, MainSystem.Vreg.Length); //15 8bit regests V0-VE
            MainSystem.Opcode = new ushort();
            Array.Clear(MainSystem.FrameBuffer, 0, MainSystem.FrameBuffer.Length);
            Array.Clear(MainSystem.regRPL, 0, MainSystem.regRPL.Length);
            MainSystem.Delay_Timer = new byte();
            MainSystem.Sound_Timer = new byte();
            StopEmulation = false;
            PauseEmulation = false;
            LastKeyPressed = 0x11;
            MainSystem.Stack = new Stack();
            Random r = new Random();
            pScreen.BackColor = Color.Black;
            LoadFonts();
        }

        public void InitOpenTK()
        {
            //Initialize OpenTK/GL control/window
            m_ControlContext = Control.FromHandle(pScreen.Handle);
            m_ControlContext.Paint += new PaintEventHandler(m_ControlContext_Paint);
            m_WindowInfo = Utilities.CreateWindowsWindowInfo(pScreen.Handle);
            m_GContext = new GraphicsContext(GraphicsMode.Default, m_WindowInfo);
            m_GContext.MakeCurrent(m_WindowInfo);
            m_GContext.LoadAll();
        }

        public frmEmu()
        {
            InitializeComponent();
        }

        public void StartEmulation()
        {
            while (!StopEmulation)
            {
                //If Paused stop emulating
                if (!PauseEmulation)
                {
                    //Emulate One Cycle 
                    Interpreter();
                    EmulationExecution.WaitOne();
                }
            }

            //If we close the rom lets reset everything
            InitializeSystem();
        }
    
        public void DecodeInst(ushort Instruction)
        {
            instr.raw = Instruction;
            instr.opnum = (ushort)((Instruction & 0xF000) >> 12);
            instr.nnn = (ushort)(Instruction & 0x0FFF);
            instr.n = (byte)(Instruction & 0x000F);
            instr.x = (byte)((Instruction & 0x0F00) >> 8);
            instr.y = (byte)((Instruction & 0x00F0) >> 4);
            instr.kk = (byte)(Instruction & 0x00FF);
        }
        

        public ushort GetOpcodeMask(ushort instruction)
        {
            ushort mask = 0xF000;
            byte opcode = (byte)((instruction & 0xF000) >> 12);
           
            switch (opcode)
            {
                case 0x0:
                    {
                        if (instruction > 0)
                        {
                            if ((instruction & 0xFFF0) == 0x00C0)
                                mask = 0xFFF0;
                            else
                                mask = 0xFFFF;
                        }
                        break;
                    }
                case 0xF:
                case 0xE: mask = 0xF0FF; break;
                case 0x8: mask = 0xF00F; break;
                default: mask = 0xF000; break;
            }

            return mask;
        }
        public byte ReadMemoryByte(ushort address)
        {
            return MainSystem.Memory[address];
        }

        public void WriteMemoryByte(ushort address, byte value)
        {
                MainSystem.Memory[address] = value;
               
            if (MV.Visible)
                {
                    if (MV.hexMemory.ByteProvider.ReadByte(address) != MainSystem.Memory[address])
                        MV.hexMemory.ByteProvider.WriteByte(address, MainSystem.Memory[address]);

                    MV.hexMemory.Invalidate();
                }
        }

        public void ScrollScreenLeft()
        {
            byte[] ScrollLeftTemp = new byte[MainSystem.FrameBuffer.Length];

            for (int Line = 0; Line < MainSystem.FrameBuffer.Length; Line += 128)
            {
                int LeftScrollCount = 0;
                for (int LeftPX = 4; LeftPX < 128; LeftPX++)
                {
                    ScrollLeftTemp[Line + LeftScrollCount] = MainSystem.FrameBuffer[Line + LeftPX];
                    LeftScrollCount++;
                }
            }
            MainSystem.FrameBuffer = ScrollLeftTemp;
        }

        public void ScrollScreenRight()
        {
            byte[] ScrollRightTemp = new byte[MainSystem.FrameBuffer.Length];

            for (int Line = 0; Line < MainSystem.FrameBuffer.Length; Line += 128)
            {
                int RightScrollCount = 4;
                for (int RightPX = 0; RightPX < 124; RightPX++)
                {
                    ScrollRightTemp[Line + RightScrollCount] = MainSystem.FrameBuffer[Line + RightPX];
                    RightScrollCount++;
                }
            }
            MainSystem.FrameBuffer = ScrollRightTemp;
        }

        public void ScrollScreenDown()
        {
            byte[] ScrollDownTemp = new byte[MainSystem.FrameBuffer.Length];
            int ScrollDownPX = instr.n * 128;
            for (int B = 0; B < MainSystem.FrameBuffer.Length - 1; B++)
            {
                if (B + ScrollDownPX < ScrollDownTemp.Length)
                    ScrollDownTemp[B + ScrollDownPX] = MainSystem.FrameBuffer[B];
                else
                    break;
            }
            MainSystem.FrameBuffer = ScrollDownTemp;
        }

        public void Interpreter()
        {
            if (MainSystem.PC <= MainSystem.Memory.Length - 2)
            {

                //Merge Instructions
                MainSystem.Opcode = (ushort)(MainSystem.Memory[MainSystem.PC++] << 8 | MainSystem.Memory[MainSystem.PC++]);

                //Decode Opcode into Inst Struct
                DecodeInst(MainSystem.Opcode);

                //Log Instructions
                DB.BuildInstructionList((ushort)(MainSystem.PC-2), MainSystem.Memory[MainSystem.PC-2], MainSystem.Memory[MainSystem.PC-1], BreakPoint);

                if (StepOut && MainSystem.PC - 2 == StepOutValue)
                {
                    BreakPoint = true;
                    StepOutValue = 0;
                    StepOut = false;
                }

                //If Breakpoint Update Debugger Intructions
                if (BreakPoint)
                    DB.UpdateInstructions();

                while (BreakPoint)
                {
                    //Wait for clear
                    if (StepInto)
                    {
                        StepInto = false;
                        break;
                    }
                }

                if (!StepOver)
                {
                    switch (instr.raw & GetOpcodeMask(instr.raw))
                    {
                        case 0x00C0: //Scroll Screen Down Npx
                            ScrollScreenDown();
                            break;
                        case 0x00E0: //CLS - Clear the display.
                            Array.Clear(MainSystem.FrameBuffer, 0, MainSystem.FrameBuffer.Length);
                            m_ControlContext.Invalidate();
                            m_DrawWait.WaitOne();
                            break;
                        case 0x00EE: //RET - Return from a subroutine. The interpreter sets the program counter to the address at the top of the stack, then subtracts 1 from the stack pointer.
                            if (MainSystem.Stack.Count > 0)
                                MainSystem.PC = (ushort)MainSystem.Stack.Pop();
                            else
                                MainSystem.PC = 512;
                            break;
                        case 0x00FB: //Scroll Right 4px
                            ScrollScreenRight();
                            break;
                        case 0x00FC: //Scroll Left 4px
                            ScrollScreenLeft();
                            break;
                        case 0x00FD:
                            break;
                        case 0x00FE: //Disable extended screen mode
                            ScreenWidth = 64;
                            ScreenHeight = 32;
                            MainSystem.FrameBuffer = new byte[ScreenWidth * ScreenHeight]; 
                            break;
                        case 0x00FF: //Enable extended screen mode
                            ScreenWidth = 128;
                            ScreenHeight = 64;
                            MainSystem.FrameBuffer = new byte[ScreenWidth * ScreenHeight];
                            LoadFonts(true);
                            break;
                        case 0x1000: //JP - Jump to location nnn. The interpreter sets the program counter to nnn.
                            MainSystem.PC = instr.nnn;
                            break;
                        case 0x2000: //JSR - Push current PC to stack, and set PC = nnn.         
                            MainSystem.Stack.Push(MainSystem.PC);
                            MainSystem.PC = instr.nnn;
                            break;
                        case 0x3000: //SE - Skip next instruction if Vx = kk. The interpreter compares register Vx to kk, and if they are equal, increments the program counter by 2.
                            if (MainSystem.Vreg[instr.x] == instr.kk)
                                MainSystem.PC += 2;
                            break;
                        case 0x4000: //SNE - Skip next instruction if Vx != kk. The interpreter compares register Vx to kk, and if they are not equal, increments the program counter by 2. 
                            if (MainSystem.Vreg[instr.x] != instr.kk)
                                MainSystem.PC += 2;
                            break;
                        case 0x5000: //SE - Skip next instruction if Vx = Vy. The interpreter compares register Vx to register Vy, and if they are equal, increments the program counter by 2.
                            if (MainSystem.Vreg[instr.x] == MainSystem.Vreg[instr.y])
                                MainSystem.PC += 2;
                            break;
                        case 0x6000: //LD Set Vx = kk. The interpreter puts the value kk into register Vx.
                            MainSystem.Vreg[instr.x] = (byte)instr.kk;
                            break;
                        case 0x7000: //ADD Set Vx = Vx + kk. Adds the value kk to the value of register Vx, then stores the result in Vx.
                            MainSystem.Vreg[instr.x] += (byte)instr.kk;
                            break;
                        case 0x8000: //LD - Set Vx = Vy. Stores the value of register Vy in register Vx.
                            MainSystem.Vreg[instr.x] = MainSystem.Vreg[instr.y];
                            break;
                        case 0x8001: //OR -Set Vx = Vx OR Vy.
                            MainSystem.Vreg[instr.x] |= MainSystem.Vreg[instr.y];
                            MainSystem.Vreg[0xF] = 0;
                            break;
                        case 0x8002: //AND - Set Vx = Vx AND Vy.
                            MainSystem.Vreg[instr.x] &= MainSystem.Vreg[instr.y];
                            MainSystem.Vreg[0xF] = 0;
                            break;
                        case 0x8003: //XOR - Set Vx = Vx XOR Vy.
                            MainSystem.Vreg[instr.x] ^= MainSystem.Vreg[instr.y];
                            MainSystem.Vreg[0xF] = 0;
                            break;
                        case 0x8004: //ADD - Set Vx = Vx + Vy, set VF = carry.
                            int result = MainSystem.Vreg[instr.x] + MainSystem.Vreg[instr.y];
                            if (result > 0xFF)
                                MainSystem.Vreg[0xF] = 1;
                            else
                                MainSystem.Vreg[0xF] = 0;

                            MainSystem.Vreg[instr.x] = (byte)(result);
                            break;
                        case 0x8005: //SUB - Set Vx = Vx - Vy, set VF = NOT borrow.
                            if (MainSystem.Vreg[instr.x] >= MainSystem.Vreg[instr.y])
                                MainSystem.Vreg[0xF] = 1;
                            else
                                MainSystem.Vreg[0xF] = 0;

                            MainSystem.Vreg[instr.x] -= MainSystem.Vreg[instr.y];
                            break;
                        case 0x8006: //SHR - Set Vx = Vx SHR 1.
                            if ((MainSystem.Vreg[instr.x] & 1) == 1)
                                MainSystem.Vreg[0xF] = 1;
                            else
                                MainSystem.Vreg[0xF] = 0;

                            MainSystem.Vreg[instr.x] /= 2;
                            break;
                        case 0x8007: //SUBN - Set Vx = Vy - Vx, set VF = NOT borrow.
                            if (MainSystem.Vreg[instr.y] >= MainSystem.Vreg[instr.x])
                                MainSystem.Vreg[0xF] = 1;
                            else
                                MainSystem.Vreg[0xF] = 0;

                            MainSystem.Vreg[instr.x] = (byte)((MainSystem.Vreg[instr.y] - MainSystem.Vreg[instr.x]));
                            break;
                        case 0x800E: //SHL - Set Vx = Vx SHL 1.
                            if (((MainSystem.Vreg[instr.x] & 0x80) >> 7) == 1)
                                MainSystem.Vreg[0xF] = 1;
                            else
                                MainSystem.Vreg[0xF] = 0;

                            MainSystem.Vreg[instr.x] *= 2;
                            break;
                        case 0x9000: //SNE - Skip next instruction if Vx != Vy.
                            if (MainSystem.Vreg[instr.x] != MainSystem.Vreg[instr.y])
                                MainSystem.PC += 2;
                            break;
                        case 0xA000: //LD - Set I = nnn.
                            MainSystem.AddressRegister = instr.nnn;
                            break;
                        case 0xB000: //JMP - Jump to location nnn + V0.
                            MainSystem.PC = (ushort)(instr.nnn + MainSystem.Vreg[0]);
                            break;
                        case 0xC000: //RND - Set Vx = random byte AND kk.
                            MainSystem.Vreg[instr.x] = (byte)(r.Next(255) & instr.kk);
                            break;
                        case 0xD000: //DRW - Draw Screen.
                                Stopwatch stopwatch = new Stopwatch();
                                stopwatch.Start();
                                ushort x = MainSystem.Vreg[instr.x];
                                ushort y = MainSystem.Vreg[instr.y];

                                int pixel;
                            
                                MainSystem.Vreg[0xF] = 0;

                                if (instr.n != 0) //Chip-8
                                {
                                    try
                                    {
                                        for (ushort yline = 0; yline < instr.n; yline++)
                                        {
                                            pixel = MainSystem.Memory[MainSystem.AddressRegister + yline];
                                            for (int xline = 0; xline < 8; xline++)
                                            {
                                                if ((pixel & (0x80 >> xline)) != 0)
                                                {

                                                    if ((x + xline + ((y + yline) * ScreenWidth)) < MainSystem.FrameBuffer.Length - 1)
                                                    {
                                                        if (MainSystem.FrameBuffer[(x + xline + ((y + yline) * ScreenWidth))] == 1)
                                                            MainSystem.Vreg[0xF] = 1;

                                                        MainSystem.FrameBuffer[x + xline + ((y + yline) * ScreenWidth)] ^= 1;
                                                    }
                                                    else
                                                    {
                                                        if (MainSystem.FrameBuffer[MainSystem.FrameBuffer.Length - 1] == 1)
                                                            MainSystem.Vreg[0xF] = 1;

                                                        MainSystem.FrameBuffer[MainSystem.FrameBuffer.Length - 1] ^= 1;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    catch { }
                                }
                                else //SuperChip
                                {
                                    try
                                    {
                                        for (ushort yline = 0; yline < 16; yline++)
                                        {
                                           pixel = (MainSystem.Memory[MainSystem.AddressRegister + (yline << 1)] << 8) | MainSystem.Memory[MainSystem.AddressRegister + (yline << 1) + 1];
                                            for (int xline = 0; xline < 16; xline++)
                                            {
                                                if ((pixel & (0x8000 >> xline)) != 0)
                                                {

                                                    if ((x + xline + ((y + yline) * ScreenWidth)) < MainSystem.FrameBuffer.Length - 1)
                                                    {
                                                        if (MainSystem.FrameBuffer[(x + xline + ((y + yline) * ScreenWidth))] == 1)
                                                            MainSystem.Vreg[0xF] = 1;

                                                        MainSystem.FrameBuffer[x + xline + ((y + yline) * ScreenWidth)] ^= 1;
                                                    }
                                                    else
                                                    {
                                                        if (MainSystem.FrameBuffer[MainSystem.FrameBuffer.Length - 1] == 1)
                                                            MainSystem.Vreg[0xF] = 1;

                                                        MainSystem.FrameBuffer[MainSystem.FrameBuffer.Length - 1] ^= 1;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    catch { }
                                }


                                if (EnableDeflicker)
                                {
                                    if (MainSystem.Vreg[0xF] != 1)
                                    {
                                        m_ControlContext.Invalidate();
                                        m_DrawWait.WaitOne();
                                    }
                                    else
                                        Thread.Sleep(EmulationSpeed);
                                }
                                else
                                {
                                    m_ControlContext.Invalidate();
                                    m_DrawWait.WaitOne();
                                }
                            stopwatch.Stop();
                            if (stopwatch.Elapsed.Milliseconds != 0)
                                FPS = (int)Math.Round((decimal)1000 / stopwatch.Elapsed.Milliseconds);
                            break;
                        case 0xE09E: //SKP - Skip next instruction if key with the value of Vx is pressed.
                            if (MainSystem.Vreg[instr.x] == LastKeyPressed)
                                MainSystem.PC += 2;
                            break;
                        case 0xE0A1: //SKNP - Skip next instruction if key with the value of Vx is not pressed.
                            if (MainSystem.Vreg[instr.x] != LastKeyPressed)
                                MainSystem.PC += 2;
                            break;
                        case 0xF007: //LD - Set Vx = delay timer value.
                            if (MainSystem.Delay_Timer < 0)
                                MainSystem.Delay_Timer = 0;
                            MainSystem.Vreg[instr.x] = (byte)(MainSystem.Delay_Timer);
                            break;
                        case 0xF00A: //LD - Wait for a key press, store the value of the key in Vx.
                            while (LastKeyPressed == 0x11)
                            {
                                //Wait for Input
                            }
                            MainSystem.Vreg[instr.x] = LastKeyPressed;
                            LastKeyPressed = 0x11;
                            break;
                        case 0xF015: //LD - Set delay timer = Vx.
                            MainSystem.Delay_Timer = MainSystem.Vreg[instr.x];
                            break;
                        case 0xF018: //LD - Set sound timer = Vx.
                            MainSystem.Sound_Timer = MainSystem.Vreg[instr.y];
                            break;
                        case 0xF01E: //ADD - Set I = I + Vx.
                            if ((MainSystem.AddressRegister + MainSystem.Vreg[instr.x]) >= 0x1000)
                            {
                                MainSystem.AddressRegister = (ushort)(MainSystem.Memory.Length + 512);
                                MainSystem.Vreg[0xF] = 1;
                            }
                            else
                                MainSystem.AddressRegister += MainSystem.Vreg[instr.x];
                            break;
                        case 0xF029: //LD - Set I = location of sprite for digit Vx.
                            MainSystem.AddressRegister = (ushort)(MainSystem.Vreg[instr.x] * 5);
                            break;
                        case 0xF030:
                            MainSystem.AddressRegister = (ushort)(80 +(MainSystem.Vreg[instr.x] * 10));
                            break;
                        case 0xF033: //LD - Store BCD representation of Vx in memory locations I, I+1, and I+2.
                            byte val = MainSystem.Vreg[instr.x];
                            WriteMemoryByte((ushort)(MainSystem.AddressRegister), (byte)(val / 100));
                            WriteMemoryByte((ushort)(MainSystem.AddressRegister + 1), (byte)((val % 100) / 10));
                            WriteMemoryByte((ushort)(MainSystem.AddressRegister + 2), (byte)((val % 100) % 10));
                            break;
                        case 0xF055: //LD - Store registers V0 through Vx in memory starting at location I.
                            for (var i = 0; i <= instr.x; i++)
                                WriteMemoryByte((ushort)(MainSystem.AddressRegister + i), (byte)(MainSystem.Vreg[i]));

                            break;
                        case 0xF065: //LD - Read registers V0 through Vx from memory starting at location I.
                            for (var i = 0; i <= instr.x; i++)
                                MainSystem.Vreg[i] = ReadMemoryByte((ushort)(MainSystem.AddressRegister + i));

                            break;
                        case 0xF075:
                            for (var i = 0; i <= instr.x; i++)
                                MainSystem.regRPL[i] = MainSystem.Vreg[i];
                            break;
                        case 0xF085:
                            for (var i = 0; i <= instr.x; i++)
                                MainSystem.Vreg[i] = MainSystem.regRPL[i];
                            break;
                        default:
                            break;
                    }

                }
                if (StepOver)
                {
                    StepOver = false;
                    StepInto = false;
                }
            }
            EmulationExecution.Set();       
        }

        private void LoadFonts(bool SuperChip = false)
        {
            //Clear Any Previous Font
            for (int x = 0; x < 160; x++)
                MainSystem.Memory[x] = 0;

            //Definte Font Holder
            byte[] FontArray;

            FontArray = Properties.Resources.c8fnt;
            for (int x = 0; x < FontArray.Length; x++)
                MainSystem.Memory[x] = FontArray[x];
                FontArray = Properties.Resources.scfnt;
            for (int x = 0; x < FontArray.Length; x++)
                MainSystem.Memory[x + 80] = FontArray[x];

        }

        public void ResetGame()
        {
            if (StopEmulation == false)
            {
                StopEmulation = true;
                if (EmuThread != null)
                {
                    EmuThread.Abort();
                    while (EmuThread.ThreadState == System.Threading.ThreadState.Running)
                    {
                        //Wait Till previous thread exits
                    }
                }
                MainSystem.Stack.Clear();
                DB.ResetDebugger();
                Array.Clear(MainSystem.FrameBuffer, 0, MainSystem.FrameBuffer.Length);
                Array.Clear(MainSystem.Vreg, 0, MainSystem.Vreg.Length);
                Array.Clear(MainSystem.regRPL, 0, MainSystem.regRPL.Length);
                MainSystem.AddressRegister = 0;
                StopEmulation = false;
                MainSystem.PC = 512;
                Array.Copy(ResetMemory, MainSystem.Memory, 4096);
                MV.Mainform = this;
                MV.hexMemory.ByteProvider = new Be.Windows.Forms.DynamicByteProvider(MainSystem.Memory);
                PauseEmulation = false;
                EmuThread = new Thread(new ThreadStart(StartEmulation));
                EmuThread.IsBackground = true;
                EmuThread.Start();
                timer1.Enabled = true;
            }
        }

        public void CloseRom()
        {
            timer1.Enabled = false;
            StopEmulation = true;
            if (EmuThread != null)
            {
                EmuThread.Abort();
                while (EmuThread.ThreadState == System.Threading.ThreadState.Running)
                {
                    //Wait Till previous thread exits
                }
            }
            Array.Clear(MainSystem.FrameBuffer, 0, MainSystem.FrameBuffer.Length);
            Array.Clear(MainSystem.Vreg, 0, MainSystem.Vreg.Length);
            Array.Clear(MainSystem.regRPL, 0, MainSystem.regRPL.Length);
            MainSystem.AddressRegister = 0;
            MainSystem.PC = 512;
            if (MainSystem.Stack != null)
            MainSystem.Stack.Clear();
            m_ControlContext.Invalidate();
            toolStripStatusLabel1.Text = "Emulation Stopped";
        }
        private void loadRomToolStripMenuItem_Click(object sender, EventArgs e)
        {
            PauseEmulation = true;
            //Read file into temp array
            byte[] Temp = new Byte[0];
            OpenFileDialog fd = new OpenFileDialog();
            fd.Filter = "ch8 files (*.ch8)|*.ch8|All files (*.*)|*.*";
            if (fd.ShowDialog() == DialogResult.OK)
            {
                StopEmulation = true; //Stop any previous emulation
                PauseEmulation = false;
                CH.ResetCheats();
                MD5Hash = CH.CreateMD5(fd.FileName);
                CH.LoadCheats(MD5Hash);
                CloseRom();
                SetDefaultSpeed();
                ScreenWidth = 64;
                ScreenHeight = 32;
                MainSystem.FrameBuffer = new byte[ScreenWidth * ScreenHeight];
                using (FileStream fsSource = new FileStream(fd.FileName, FileMode.Open, FileAccess.Read))
                {

                    if ((int)fsSource.Length + 512 > MainSystem.Memory.Length)
                    {
                        MessageBox.Show("Not a valid Chip8 game. Please choose another game.", "Invalid Chip8 Game", MessageBoxButtons.OK);
                        this.Text = "Chip-Eight (C# Chip-8 Emulator)";
                        return;
                    }

                    RomName = Path.GetFileNameWithoutExtension(fd.FileName);
  
                    this.Text = "Chip-Eight (C# Chip-8 Emulator) [" + RomName + "]";

                    Temp = new byte[fsSource.Length];
                    int numBytesToRead = (int)fsSource.Length;
                    int numBytesRead = 0;
                    while (numBytesToRead > 0)
                    {
                        int n = fsSource.Read(Temp, numBytesRead, numBytesToRead);
                        if (n == 0)
                            break;

                        numBytesRead += n;
                        numBytesToRead -= n;
                    }
                    numBytesToRead = Temp.Length;
                }
                
                //Set Memory, Flags, Registers, etc... to default values
                InitializeSystem();

                //Load rom into memory from temp array
                for (int x = 0; x < Temp.Length; x++)
                    MainSystem.Memory[x + 512] = Temp[x];

                MV.Mainform = this;
                MV.hexMemory.ByteProvider = new Be.Windows.Forms.DynamicByteProvider(MainSystem.Memory);
                DB.ResetDebugger(true);
                DB.Mainform = this;

                Array.Copy(MainSystem.Memory, ResetMemory, 4096); //Save Loaded Game for Reset
                EmuThread = new Thread(new ThreadStart(StartEmulation));
                EmuThread.IsBackground = true;
                EmuThread.Start();
                timer1.Enabled = true;
            }
            PauseEmulation = false;
         }

        private void closeRomToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Text = "Chip-Eight (C# Chip-8 Emulator)";
            CH.ResetCheats();
            CloseRom();
            DB.ResetDebugger(true);
            SetDefaultSpeed();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (StopEmulation == true)
            {
                toolStripStatusLabel1.Text = "Emulation Stopped";
                lblfps.Text = "FPS: 0";
            }
            else
            {
                toolStripStatusLabel1.Text = "Emulation Started";
                lblfps.Text = "FPS: " + FPS.ToString();
            }

        }


        private void frmEmu_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.D0:
                    LastKeyPressed = 0x0;
                    break;
                case Keys.D1:
                    LastKeyPressed = 0x01;
                    break;
                case Keys.D2:
                    LastKeyPressed = 0x02;
                    break;
                case Keys.D3:
                    LastKeyPressed = 0x03;
                    break;
                case Keys.D4:
                    LastKeyPressed = 0x04;
                    break;
                case Keys.D5:
                    LastKeyPressed = 0x05;
                    break;
                case Keys.D6:
                    LastKeyPressed = 0x06;
                    break;
                case Keys.D7:
                    LastKeyPressed = 0x07;
                    break;
                case Keys.D8:
                    LastKeyPressed = 0x08;
                    break;
                case Keys.D9:
                    LastKeyPressed = 0x09;
                    break;
                case Keys.Q:
                    LastKeyPressed = 0x0A;
                    break;
                case Keys.W:
                    LastKeyPressed = 0x0B;
                    break;
                case Keys.E:
                    LastKeyPressed = 0x0C;
                    break;
                case Keys.R:
                    LastKeyPressed = 0x0D;
                    if (Control.ModifierKeys == Keys.Control)
                        ResetGame();
                    break;
                case Keys.T:
                    LastKeyPressed = 0x0E;
                    break;
                case Keys.Y:
                    LastKeyPressed = 0x0F;
                    break;
                case Keys.P: //Pause
                    if (Control.ModifierKeys == Keys.Control)
                        PauseEmulation ^= true;
                    break;
                case Keys.Oemplus: //Increase Speed
                    IncreaseSpeed();
                    break;
                case Keys.OemMinus: //Decrease Speeed
                    DecreaseSpeed();
                    break;
                case Keys.D: //Show Debugger
                    if (Control.ModifierKeys == Keys.Control)
                    {
                        DB.Mainform = this;
                        DB.Show();
                        DB.Activate();
                    }
                    break;
                case Keys.M: //Show Memory Viewer
                    if (Control.ModifierKeys == Keys.Control)
                    {
                        MV.Mainform = this;
                        MV.Show();
                        MV.Activate();
                    }
                    break;
                case Keys.C: //Show Memory Viewer
                    if (Control.ModifierKeys == Keys.Control)
                    {
                        CH.MainForm = this;
                        CH.Show();
                        CH.Activate();
                    }
                    break;
                case Keys.S: //Show Memory Viewer
                    if (Control.ModifierKeys == Keys.Control)
                        SetDefaultSpeed();
                    break;
                default:
                    break;
            }
        }

        public void IncreaseSpeed()
        {
            if (EmulationSpeed > 2)
                EmulationSpeed -= 2;
        }

        public void DecreaseSpeed()
        {
            if (EmulationSpeed < 60)
                EmulationSpeed += 2;
        }

        public void SetDefaultSpeed()
        {
            EmulationSpeed = 10;
        }

        private void DelayTimer_Tick(object sender, EventArgs e)
        {
            if (MainSystem.Delay_Timer > 0)
                MainSystem.Delay_Timer--;

            if (MainSystem.Sound_Timer > 0)
                MainSystem.Sound_Timer--;
            
            //Display input
            if (LastKeyPressed.ToString("X") != "11")
                this.KeyPressed.Text = "Last Key Pressed (" + LastKeyPressed.ToString("X") + ")";    
            else
                this.KeyPressed.Text = "Last Key Pressed (None)"; 
        }


        private void pausePToolStripMenuItem_Click(object sender, EventArgs e)
        {
            PauseEmulation ^= true;
        }

        private void resetToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ResetGame();

        }
 
        void m_ControlContext_Paint(object sender, PaintEventArgs e)
        {
            if (!m_GContext.IsCurrent)
                Console.WriteLine("Warning context isn't on current thread!");

            GL.Viewport(m_ControlContext.ClientRectangle);

            float m_ScaleX = (float)m_ControlContext.Width / (float)64;
            float m_ScaleY = (float)m_ControlContext.Height / (float)32;

            //Set Clear Color
            GL.ClearColor(Color.FromArgb(Color.Black.ToArgb()));
            GL.Clear(ClearBufferMask.ColorBufferBit);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
            OpenTK.Matrix4 matrix = OpenTK.Matrix4.CreateOrthographicOffCenter(0, (float)m_ControlContext.Width, (float)m_ControlContext.Height, 0, -1, 100);
            GL.LoadMatrix(ref matrix);
            GL.Begin(BeginMode.Quads);
            
            //Set Draw Color
            if (ScreenWidth != 128) 
                GL.Color4(Color.FromArgb(Color.Lime.ToArgb()));
            else
                GL.Color4(Color.FromArgb(Color.Orange.ToArgb()));
         
            //Set new scal from original screen size
            float xscale = ((float)m_ControlContext.Width / (float)ScreenWidth);
            float yscale = ((float)m_ControlContext.Height / (float)ScreenHeight);
            int a = 0;
            int PixelCounter = 0;

            for (int b = 0; b < ScreenHeight; b++)
            {
                while (a < MainSystem.FrameBuffer.Length)
                {
                    if (PixelCounter < ScreenWidth)
                    {
                        if (MainSystem.FrameBuffer[a] == 1)
                        {
                            //Draw on Vertex
                            GL.Vertex2(PixelCounter * xscale, b * yscale);
                            GL.Vertex2((PixelCounter * xscale) + xscale, b * yscale);
                            GL.Vertex2((PixelCounter * xscale) + xscale, (b * yscale) + yscale);
                            GL.Vertex2(PixelCounter * xscale, (b * yscale) + yscale);
                        }
                    }

                    if (PixelCounter == ScreenWidth)
                    {
                        PixelCounter = 0;
                        break;
                    }

                    PixelCounter++;
                    a++;
                }
            }

            GL.End();
            GL.LoadIdentity();

            //Update Screen
            m_GContext.SwapBuffers();

            Thread.Sleep(EmulationSpeed);
            m_DrawWait.Set();
        }

        public void ReadConfigFile()
        {
            string Path = Application.StartupPath + @"\config.sys";
            if (File.Exists(Path))
            {
                using (StreamReader sr = new StreamReader(Path))
                {

                }
            }
            else
            {
                using (StreamWriter sr = new StreamWriter(Path))
                {

                }
            }
        }

        private void frmEmu_Load(object sender, EventArgs e)
        {
            InitOpenTK();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void debuggerToolStripMenuItem_Click(object sender, EventArgs e)
        {
               DB.Mainform = this;
               DB.Show();
               DB.Activate();
        }

        private void memoryViewerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MV.Mainform = this;
            MV.Show();
            MV.Activate();
        }

        private void frmEmu_KeyUp(object sender, KeyEventArgs e)
        {
            LastKeyPressed = 0x11; //Reset LastKeyPressed when releasing key
        }

        private void cheatsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CH.MainForm = this;
            CH.Show();
            CH.Activate();
        }

        private void enableDeflickerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            EnableDeflicker ^= true;
        }

        private void setDefaultCtrlSToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetDefaultSpeed();
        }

        private void increaseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IncreaseSpeed();
        }

        private void decreaseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DecreaseSpeed();
        }

        private void aboutToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            using (frmAbout frm = new frmAbout())
            {
                frm.ShowDialog();
            }
        }
    }
}
